package com.ideaaedi.mybatis.data.security.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.ideaaedi.mybatis.data.security.interceptor.MybatisEncryptPlugin;
import com.ideaaedi.mybatis.data.security.support.EncryptExecutor;
import com.ideaaedi.mybatis.data.security.support.EncryptParser;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorChain;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 总配置类
 *
 * @author JustryDeng
 * @since 2021/7/19 2:49:24
 */
@org.springframework.context.annotation.Configuration
@AutoConfigureAfter(value = {Configuration.class, SqlSessionFactory.class})
public class MybatisDataSecurityConfiguration {
    
    /**
     * 解析器
     */
    @Bean
    public EncryptParser encryptParser(@Autowired ApplicationContext applicationContext, @Autowired EncryptExecutor encryptExecutor) {
        EncryptParser encryptParser = new EncryptParser(applicationContext, encryptExecutor);
        // 添加mybatis拦截器
        addInterceptor(encryptParser);
        return encryptParser;
    }
    
    /**
     * 添加mybatis拦截器
     */
    private void addInterceptor(EncryptParser encryptParser) {
        MybatisEncryptPlugin mybatisEncryptPlugin = new MybatisEncryptPlugin(encryptParser);
        encryptParser.getSqlSessionFactoryList().forEach(sqlSessionFactory -> {
            Configuration configuration = sqlSessionFactory.getConfiguration();
            if (configuration == null) {
                return;
            }
            // 添加插件（如果存在mp插件的话，本插件要添加在其前面，保证本插件更贴近target（即：入时比mp插件后执行；出时比mp插件先执行））
            List<Interceptor> interceptors = new ArrayList<>(configuration.getInterceptors());
            int mybatisPlusInterceptorIdx = -1;
            for (int i = 0; i < interceptors.size(); i++) {
                if (interceptors.get(i) instanceof MybatisPlusInterceptor) {
                    mybatisPlusInterceptorIdx = i;
                    break;
                }
            }
            if (mybatisPlusInterceptorIdx >= 0) {
                interceptors.add(mybatisPlusInterceptorIdx, mybatisEncryptPlugin);
            } else {
                interceptors.add(mybatisEncryptPlugin);
            }

            Field interceptorChainField = ReflectionUtils.findField(Configuration.class, "interceptorChain");
            boolean icfAccessible = interceptorChainField.isAccessible();
            interceptorChainField.setAccessible(true);
            InterceptorChain interceptorChain = (InterceptorChain)ReflectionUtils.getField(interceptorChainField, configuration);
            Field interceptorsField = ReflectionUtils.findField(InterceptorChain.class, "interceptors");
            boolean ifAccessible = interceptorsField.isAccessible();
            interceptorsField.setAccessible(true);
            List<Interceptor> finalInterceptors = (List<Interceptor>)ReflectionUtils.getField(interceptorsField, interceptorChain);
            interceptorChainField.setAccessible(icfAccessible);
            interceptorsField.setAccessible(ifAccessible);
            assert finalInterceptors != null;
            finalInterceptors.clear();
            finalInterceptors.addAll(interceptors);
        });
    }
}
